<template>
  <div class="storey">
    <div class="storey_GL" ref="gl"></div>
  </div>
</template>

<script>
import * as THREE from "three";
import { TweenMax, Cubic } from "gsap";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer'
import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass'
import { OutlinePass } from 'three/examples/jsm/postprocessing/OutlinePass'
import { ShaderPass } from 'three/examples/jsm/postprocessing/ShaderPass'
import { FXAAShader } from 'three/examples/jsm/shaders/FXAAShader'
import { CSS2DRenderer, CSS2DObject } from 'three/examples/jsm/renderers/CSS2DRenderer'

export default {
  data() {
    return {
      scene: null,
      camera: null,
      renderer: null,
      labelRenderer: null,
      controls: null,
      composer: null,
      length: 8, // 长
      width: 5, // 宽
      height: .1, // 高
      wallHeight: 1, // 墙体高度
      storeyGroup: null,
      stores: [],
      mixer: null,
      clock: null,
      isRight: null
    };
  },
  mounted() {
    this.init();
    window.addEventListener(
      "resize",
      () => {
        this.camera.aspect = window.innerWidth / window.innerHeight;
        this.camera.updateProjectionMatrix();
        this.renderer.setSize(window.innerWidth, window.innerHeight);
        this.labelRenderer.setSize(window.innerWidth, window.innerHeight);
      },
      false
    );
  },
  methods: {
    init() {
      this.initScene()
      this.initCamera()
      this.initLight()
      this.initRenderer()
      // this.initGridHelper()
      this.initControls()
      this.initbuilding()
      this.Update();
    },
    // 创建场景
    initScene() {
      this.scene = new THREE.Scene();
      this.scene.background = new THREE.Color(0x222222);
    },
    // 摄像机
    initCamera() {
      this.camera = new THREE.PerspectiveCamera(
        45,
        window.innerWidth / window.innerHeight,
        1,
        1000
      );
    },
    initLight() {
      // 环境光
      let light = new THREE.AmbientLight(0xfafafa, .5);
      this.scene.add(light);
    },
    // 渲染器
    initRenderer() {
      let gl = this.$refs.gl;
      this.renderer = new THREE.WebGLRenderer({
        antialias: true,
      });
      this.renderer.setPixelRatio(window.devicePixelRatio);
      this.renderer.setSize(window.innerWidth, window.innerHeight);
      this.labelRenderer = new CSS2DRenderer();
      this.labelRenderer.setSize(window.innerWidth, window.innerHeight);
      this.labelRenderer.domElement.style.position = 'absolute';
      this.labelRenderer.domElement.style.top = 0;
      gl.appendChild(this.renderer.domElement);
      gl.appendChild(this.labelRenderer.domElement)
    },
    // 控制器
    initControls() {
      // 鼠标点击事件
      this.labelRenderer.domElement.addEventListener('pointerdown', e => {
        this.isRight = false
        if(e.which === 3) {
          this.isRight = true
        }
        if(e.which === 1) {
          e.target.parentElement.click()
        }
      })
      // 滚轮事件
      this.labelRenderer.domElement.addEventListener('wheel', () => {
        this.isRight = false
      })
      this.controls = new OrbitControls(this.camera, this.labelRenderer.domElement);
      this.controls.enableDamping = true;
      this.controls.dampingFactor = 0.25;
      this.controls.maxDistance = 800;
      this.controls.maxPolarAngle = Math.PI / 2;
      this.controls.minAzimuthAngle = Math.PI / 2;
    },
    // 网格
    initGridHelper() {
      let gh = new THREE.GridHelper(
        10,
        10,
        new THREE.Color(0x555555),
        new THREE.Color(0x333333)
      );
      this.scene.add(gh);
    },
    // 光线投射
    initRaycaster() {
      let raycaster = new THREE.Raycaster();
      let mouse = new THREE.Vector2();
      let composer = new EffectComposer(this.renderer)
      let renderPass = new RenderPass(this.scene, this.camera)
      composer.addPass(renderPass)
      let outlinePass = new OutlinePass(new THREE.Vector2(window.innerWidth, window.innerHeight), this.scene, this.camera)

      outlinePass.edgeStrength = 5 // 包围线浓度
      outlinePass.edgeGlow = 0.5 // 边缘线范围
      outlinePass.edgeThickness = 2 // 边缘线浓度
      outlinePass.pulsePeriod = 0 // 包围线闪烁频率
      outlinePass.visibleEdgeColor.set(0xff6600) // 包围线颜色
      outlinePass.hiddenEdgeColor.set(0xff6600) // 被遮挡边界线颜色

      composer.addPass(outlinePass)
      let effectFXAA = new ShaderPass(FXAAShader)
      effectFXAA.uniforms['resolution'].value.set(1/window.innerWidth, 1/window.innerHeight)
      effectFXAA.renderToScreen = true
      composer.addPass(effectFXAA)

      let findGroup = (item) => {
        if(typeof item.name === 'string' && item.name.includes('store_')) {
          return item
        } else if (item.parent) {
          return findGroup(item.parent)
        } else {
          return null
        }
      }
      window.addEventListener('mousemove', e => {
        outlinePass.selectedObjects = []
        mouse.x = ( e.clientX / window.innerWidth ) * 2 - 1;
        mouse.y = - ( e.clientY / window.innerHeight ) * 2 + 1;
        raycaster.setFromCamera(mouse, this.camera);
        let intersect = raycaster.intersectObjects([this.scene], true);
        if(intersect.length > 0) {
          let [firstObj] = intersect
          if(firstObj) {
            let g = findGroup(firstObj.object)
            if (!g) return
            outlinePass.selectedObjects = [g]
          }
        }
      }, false);
      window.addEventListener('dblclick', e => {
        mouse.x = ( e.clientX / window.innerWidth ) * 2 - 1;
        mouse.y = - ( e.clientY / window.innerHeight ) * 2 + 1;
        raycaster.setFromCamera(mouse, this.camera);
        let intersect = raycaster.intersectObjects([this.scene], true);
        if(intersect.length > 0) {
          let [firstObj] = intersect
          if(firstObj) {
            let g = findGroup(firstObj.object)
            if (!g || !g.visible) return
            this.stores.forEach(item => {
              item.visible = false
              item.getObjectByName('storeyTag').visible = false
              let roomArea = item.getObjectByName('roomArea')
              roomArea.children.forEach(qy => {
                if (qy.name.includes('区域')) {
                  qy.visible = false
                }
              })
              if (item.name === g.name) {
                let r = this.length
                let y = item.position.y + (this.wallHeight / 2)
                item.visible = true
                item.getObjectByName('storeyTag').visible = true
                roomArea.children.forEach(qy => {
                  if (qy.name.includes('区域')) {
                    qy.visible = true
                  }
                })
                this.setCameraPosition(r, y, 15)
              }
            })
            this.scene.getObjectByName('roof').visible = false
          }
        }
      }, false)
      // change标志位
      let changeFlag = false
      // 轨道控制中移除事件
      let removeContextmenu = () => {
        if (changeFlag) return
        if (this.isRight) {
          changeFlag = true
        }
      }
      // 轨道控制中移除事件
      this.controls.addEventListener( 'start', () => {
        this.controls.addEventListener( 'change', removeContextmenu)
        this.controls.addEventListener( 'end', () => {
          this.controls.removeEventListener('change', removeContextmenu)
        });
      });
      window.addEventListener('contextmenu', () => {
        if (changeFlag) {
          changeFlag = false
          return
        }
        this.stores.forEach(item => {
          item.visible = true
          item.getObjectByName('storeyTag').visible = true
          let roomArea = item.getObjectByName('roomArea')
          roomArea.children.forEach(qy => {
            if (qy.name.includes('区域')) {
              qy.visible = false
            }
          })
        })
        this.scene.getObjectByName('roof').visible = true
        this.setCameraPosition()
      })
      this.composer = composer
    },
    // 绘制楼栋
    initbuilding() {
      let meshGroup = new THREE.Group()
      meshGroup.name = 'store'
      for(let i = 0; i < 8; i++) {
        let store = this.initStorey(i + 1)
        store.name = `store_${i + 1}`
        if (i > 0) {
          let y = i * (this.wallHeight + 0.03)
          store.position.y = y
        }
        this.stores.push(store)
        meshGroup.add(store)
      }

      // 补全房顶
      let roof = this.initRoof()
      meshGroup.add(roof)
      let d = 100
      let x = d * Math.sin(65 * Math.PI / 180)
      let y = d * Math.cos(65 * Math.PI / 180)
      // 前
      let front = new THREE.DirectionalLight(0xfafafa, .7)
      front.position.set(x, y, d);
      meshGroup.add(front)
      // 后
      let behind = new THREE.DirectionalLight(0xfafafa, .7)
      behind.position.set(-x, y, -d);

      meshGroup.add(behind)
      this.scene.add(meshGroup)
      this.setCameraPosition()
      // 点击事件
      this.initRaycaster()
    },
    // 调整摄像机位置
    setCameraPosition(
      r = (this.stores.length * this.wallHeight) + this.length,
      y = this.stores.length * this.wallHeight / 2,
      angleX = 45,
      angleY = angleX
    ) {
      TweenMax.to(
        this.controls.target,
        1.5,
        {
          x: 0,
          y,
          z: 0,
          delay: 0,
          ease: Cubic.easeOut
        }
      );
      TweenMax.to(
        this.camera.position,
        1.5,
        {
          x: r * Math.sin(angleX * Math.PI / 180) + y,
          y: r * Math.cos(angleY * Math.PI / 180) + y,
          z: r,
          delay: 0,
          ease: Cubic.easeOut
        }
      );
    },
    // 绘制楼层
    initStorey(n) {
      this.storeyGroup = new THREE.Group()
      this.initFloor()
      this.initWall(n)
      return this.storeyGroup
    },
    // 绘制地板
    initFloor() {
      let shape = new THREE.Shape();
      this.drawPath(shape)
      let geometry = new THREE.ExtrudeBufferGeometry(shape, {
        curveSegments: 1,
        depth: 0.03,
        bevelSize: 1,
        bevelEnabled: false,
      });
      geometry.rotateX(Math.PI / 2)
      geometry.rotateZ(Math.PI)
      let texture = new THREE.TextureLoader().load(require('@/assets/three/floor.jpg'))
      texture.wrapS = THREE.RepeatWrapping;
      texture.wrapT = THREE.RepeatWrapping;
      texture.repeat.set(4, 4);
      let mesh = new THREE.Mesh(geometry, [
        new THREE.MeshPhongMaterial({
          map: texture
        }),
        new THREE.MeshBasicMaterial({
          color: 0x000000
        })
      ]);
      mesh.position.x = this.length / 2;
      mesh.position.z = -this.width / 2;
      mesh.name = 'floor'
      this.storeyGroup.add(mesh)
    },
    // 绘制房顶
    initRoof() {
      let shape = new THREE.Shape();
      this.drawPath(shape)
      let geometry = new THREE.ExtrudeBufferGeometry(shape, {
        curveSegments: 1,
        depth: 0.03,
        bevelSize: 1,
        bevelEnabled: false,
      });
      geometry.rotateX(Math.PI / 2)
      geometry.rotateZ(Math.PI)
      let mesh = new THREE.Mesh(geometry, [
        new THREE.MeshPhongMaterial(),
        new THREE.MeshBasicMaterial({
          color: 0x000000
        }),
      ]);
      
      mesh.position.x = this.length / 2;
      mesh.position.z = -this.width / 2;
      mesh.position.y = this.stores.length * this.wallHeight + (0.03 * this.stores.length)
      mesh.name = 'roof'
      return mesh
    },
    // 绘制墙体
    initWall(n) {
      // 前
      let frontShape = new THREE.Shape();
      this.drawPath(frontShape, this.wallHeight)
      this.genWill(frontShape, 'front')
      // 后
      let behindShape = new THREE.Shape();
      this.drawPath(behindShape, this.wallHeight)
      this.genWill(behindShape, 'behind')

      // 左
      let leftShape = new THREE.Shape();
      this.drawPath(leftShape, this.wallHeight, this.width)
      this.genWill(leftShape, 'left')

      // 右
      let rightShape = new THREE.Shape();
      this.drawPath(rightShape, this.wallHeight, this.width)
      this.genWill(rightShape, 'right')

      let roomArea = this.genRoomArea(this.random())
      roomArea.name = 'roomArea'
      this.storeyGroup.add(roomArea)

      let storeyTag = this.createStoreyTag(this.length / 2 + 1, this.wallHeight / 2, 0, n)
      storeyTag.name = `storeyTag`
      this.storeyGroup.add(storeyTag)
      // 房间添加点光源
      // let light = new THREE.PointLight(0xfafafa, .1);
      // light.position.set(0, this.wallHeight / 2, 0);

      // this.storeyGroup.add(light)
    },
    // 绘制路径
    drawPath(path, W = this.width, L = this.length) {
      let paths = [
        [0, 0],
        [0, W],
        [L, W],
        [L, 0],
        [0, 0]
      ]
      for(let i = 0; i < paths.length; i++) {
        if (i === 0) {
          path.moveTo(...paths[i])
        } else {
          path.lineTo(...paths[i])
        }
      }
    },
    // 传入shape生成墙面
    genWill(shape, type) {
      let willGroup = new THREE.Group()
      let winGroup = this.genWindow(shape, type)
      let geometry = new THREE.ExtrudeGeometry(shape, {
        curveSegments: 1,
        depth: this.height,
        bevelSize: 1,
        bevelEnabled: false,
      });
      let mesh = new THREE.Mesh(geometry, new THREE.MeshPhongMaterial());
      mesh.rotateY(Math.PI)
      mesh.position.x = this.length / 2;
      mesh.position.z = -this.width / 2;
      mesh.position.y = 0.03;
      switch (type) {
        case 'front':
          mesh.position.z = -this.width / 2 + this.height;
          break;
        case 'behind':
          mesh.position.z = this.width / 2
          break;
        case 'left':
          mesh.rotateY(Math.PI / 2)
          break;
        case 'right':
          mesh.rotateY(Math.PI / 2)
          mesh.position.x = -this.length / 2 + this.height
          break;
      }
      mesh.name = `${this.stores.length + 1}_${type}`
      willGroup.add(mesh, winGroup)
      this.storeyGroup.add(willGroup)
    },
    // 添加窗户
    genWindow(shape, type) {
      let width = type === 'front' || type === 'behind' ? this.length : this.width
      let winGroup = new THREE.Group();
      let windows = []
      let windowNum = type === 'front' || type === 'behind' ? 3 : 2
      let space = 1
      let l = 0.4
      let x = + ((width - ((windowNum * l) + (windowNum - 1) * space)) / 2).toFixed(1)
      let y = 0.3
      let geometry = new THREE.BoxGeometry(l, l, 0.03);
      let texture = new THREE.TextureLoader().load(require('@/assets/three/wd.jpg'))
      let material = new THREE.MeshPhongMaterial({
        // color: 0x409EFF,
        map: texture
      })
      for(let i = 0; i < windowNum; i++) {
        let mesh = new THREE.Mesh(geometry, material);
        mesh.name = `${this.stores.length + 1}_${type}_win${i + 1}`
        mesh.position.set(x + l / 2, y + l / 2, )
        winGroup.add(mesh)
        // 扣除部分
        let sw = new THREE.Shape()
        sw.moveTo(x, y)
        sw.lineTo(x, y + l)
        sw.lineTo(x + l, y + l)
        sw.lineTo(x + l, y)
        windows.push(sw)
        x += space + l
      }
      let center = {
        length: this.length / 2,
        width: this.width / 2,
        height: this.height / 2
      }
      winGroup.position.y = 0.03
      switch (type) {
        case 'front':
          winGroup.position.x = -center.length
          winGroup.position.z = -center.width + center.height - 0.03
          break;
        case 'behind':
          winGroup.position.x = -center.length
          winGroup.position.z = center.width - center.height + 0.03
          break
        case 'left':
          winGroup.rotateY(Math.PI / 2)
          winGroup.position.x = center.length - center.height + 0.03
          winGroup.position.z = center.width
          break
        case 'right':
          winGroup.rotateY(Math.PI / 2)
          winGroup.position.x = -center.length + center.height - 0.03
          winGroup.position.z = center.width
          break
      }
      shape.holes.push(...windows)
      return winGroup
    },
    // 绘制房间区域
    genRoomArea(n) {
      let split = 4
      let c = ['#6666FF', '#66CC66', '#9966FF', '#CC6666', '#FF66CC', '#FFCC66', '#666699', '#66CC99']
      let remain = 0
      let group = new THREE.Group()
      let x = this.length - 0.03 * 2
      let y = this.wallHeight - 0.03
      let z = this.width - 0.03 * 2
      let w = n > split ? split : n
      let startX = -x / 2 + x / w / 2
      let startY = y / 2 + 0.03
      let startZ = 0
      let q = 1
      if (n > split) {
        remain = n % split || split
      }
      for(let i = 0; i < n; i++) {
        let mesh = this.genAreaBox(x / w, y, z, c[i])
        startZ = 0
        if (i >= split - remain) {
          startZ = z / 4
          mesh = this.genAreaBox(x / w, y, z / 2, c[i])
          q = q > 2 ? 1 : q
          if (q % 2 > 0) {
            startZ = -z / 4
          } else {
            startX -= x / w
            startZ = z / 4
          }
          q++
        }
        mesh.position.x = startX
        mesh.position.y = startY
        mesh.position.z = startZ
        let text = this.createTag(startX, y, startZ, `区域_${i +1}`, c[i])
        startX += x / w
        group.add(mesh)
        group.add(text)
      }
      return group
    },
    // 获取房间box
    genAreaBox(x, y, z, c) {
      let geometry = new THREE.BoxGeometry(x, y, z);
      let material = new THREE.MeshLambertMaterial({
        color: c,
        transparent: true,
        opacity: 0.3
      })
      let mesh = new THREE.Mesh(geometry, material)
      return mesh
    },
    // 区域标签
    createTag(x, y, z, n, c) {
      const tag = document.createElement('div');
      const tline = document.createElement('div');
      const ttext = document.createElement('div');
      const tarrow = document.createElement('div');
      tag.className = 'tag'
      tline.className = 't_line'
      tline.style.background = c
      ttext.className = 't_text'
      tarrow.className = 't_arrow'
      ttext.title = n
      ttext.innerText = n
      tag.appendChild(tline)
      tag.appendChild(ttext)
      tag.appendChild(tarrow)
      tag.onclick = e => {
        console.log(n)
      }
      const label = new CSS2DObject(tag)
      label.position.set(x, y, z)
      label.name = n
      label.visible = false
      return label
    },
    // 楼层标签
    createStoreyTag(x, y, z, n) {
      const tag = document.createElement('div');
      tag.className = 'storey_tag'
      tag.innerText = n
      const label = new CSS2DObject(tag)
      label.position.set(x, y, z)
      label.name = n
      return label
    },
    // 随机颜色
    randomHexColor() {
      return '#' + ('00000' + (Math.random() * 0x1000000 << 0).toString(16)).substr(-6);
    },
    random(min = 1, max = 8) {
      return Math.floor(Math.random() * (max - min)) + min;
    },
    // 渲染场景
    Update() {
      requestAnimationFrame(this.Update);
      this.renderer.render(this.scene, this.camera);
      this.labelRenderer.render(this.scene, this.camera);
      this.composer.render();
      this.controls.update();
    },
  },
};
</script>

<style lang="scss">
.storey {
  .storey_tag {
    position: absolute;
    width: 30px;
    height: 30px;
    line-height: 30px;
    font-size: 20px;
    font-weight: bold;
    text-align: center;
    color: #fff;
    background: rgba(#ff4c00, .7);
    border: 3px solid #ff4c00;
    border-radius: 50%;
  }
  .tag {
    position: absolute;
    border-radius: 5px;
    overflow: hidden;
    cursor: pointer;
    .t_line {
      height: 10px;
      background: #ffb145;
    }
    .t_text {
      padding: 10px 20px;
      color: #fff;
      font-size: 16px;
      background: #1d3257;
      border-bottom-left-radius: 5px;
      border-bottom-right-radius: 5px;
      overflow: hidden;
      text-overflow:ellipsis;
      white-space: nowrap;
      max-width: 100px;
    }
    .t_arrow {
      margin: 0 auto;
      width: 0;
      height: 0;
      border-left: 10px solid transparent;
      border-right: 10px solid transparent;
      border-top: 10px solid #1d3257;
    }
  }
}
</style>